package io.dts.datasource.wrapper.executor;

import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.regex.Pattern;

import io.dts.parser.struct.SqlType;

/**
 * Created by guoyubo on 2017/9/23.
 */
public class SqlTypeParser {

  /**
   * 用于判断是否是一个select ... for update的sql
   */
  private static final Pattern SELECT_FOR_UPDATE_PATTERN =
      Pattern.compile("^select\\s+.*\\s+for\\s+update.*$", Pattern.CASE_INSENSITIVE);

  /**
   * 获得SQL语句种类
   */
  public static SqlType getSqlType(String sql) throws SQLException {
    SqlType sqlType = null;
    String noCommentsSql = sql;
    if (sql.contains("/*")) {
      noCommentsSql = StringUtils.stripComments(sql, "'\"", "'\"", true, false, true, true).trim();
    }
    if (StringUtils.startsWithIgnoreCaseAndWs(noCommentsSql, "select")) {
      if (noCommentsSql.toLowerCase().contains(" for ")
          && SELECT_FOR_UPDATE_PATTERN.matcher(noCommentsSql).matches()) {
        throw new SQLException(
            "only select, insert, update, delete,replace,truncate,create,drop,load,merge sql is supported");
      } else {
        sqlType = SqlType.SELECT;
      }
    } else if (StringUtils.startsWithIgnoreCaseAndWs(noCommentsSql, "insert")) {
      sqlType = SqlType.INSERT;
    } else if (StringUtils.startsWithIgnoreCaseAndWs(noCommentsSql, "update")) {
      sqlType = SqlType.UPDATE;
    } else if (StringUtils.startsWithIgnoreCaseAndWs(noCommentsSql, "delete")) {
      sqlType = SqlType.DELETE;
    } else {
      throw new SQLException(
          "only select, insert, update, delete,replace,truncate,create,drop,load,merge sql is supported");
    }
    return sqlType;
  }

  private static class StringUtils {


    /**
     * Determines whether or not the sting 'searchIn' contains the string 'searchFor', disregarding
     * case and leading whitespace
     *
     * @param searchIn the string to search in
     * @param searchFor the string to search for
     *
     * @return true if the string starts with 'searchFor' ignoring whitespace
     */
    private static boolean startsWithIgnoreCaseAndWs(String searchIn, String searchFor) {
      return startsWithIgnoreCaseAndWs(searchIn, searchFor, 0);
    }

    /**
     * Determines whether or not the sting 'searchIn' contains the string 'searchFor', disregarding
     * case and leading whitespace
     *
     * @param searchIn the string to search in
     * @param searchFor the string to search for
     * @param beginPos where to start searching
     *
     * @return true if the string starts with 'searchFor' ignoring whitespace
     */
    private static boolean startsWithIgnoreCaseAndWs(String searchIn, String searchFor,
        int beginPos) {
      if (searchIn == null) {
        return searchFor == null;
      }

      int inLength = searchIn.length();

      for (; beginPos < inLength; beginPos++) {
        if (!Character.isWhitespace(searchIn.charAt(beginPos))) {
          break;
        }
      }

      return startsWithIgnoreCase(searchIn, beginPos, searchFor);
    }

    /**
     * Determines whether or not the string 'searchIn' contains the string 'searchFor',
     * dis-regarding case starting at 'startAt' Shorthand for a String.regionMatch(...)
     *
     * @param searchIn the string to search in
     * @param startAt the position to start at
     * @param searchFor the string to search for
     *
     * @return whether searchIn starts with searchFor, ignoring case
     */
    private static boolean startsWithIgnoreCase(String searchIn, int startAt, String searchFor) {
      return searchIn.regionMatches(true, startAt, searchFor, 0, searchFor.length());
    }

    /**
     * Returns the given string, with comments removed
     *
     * @param src the source string
     * @param stringOpens characters which delimit the "open" of a string
     * @param stringCloses characters which delimit the "close" of a string, in counterpart order to
     *        <code>stringOpens</code>
     * @param slashStarComments strip slash-star type "C" style comments
     * @param slashSlashComments strip slash-slash C++ style comments to end-of-line
     * @param hashComments strip #-style comments to end-of-line
     * @param dashDashComments strip "--" style comments to end-of-line
     * @return the input string with all comment-delimited data removed
     */
    @SuppressWarnings("unused")
    private static String stripComments(String src, String stringOpens, String stringCloses,
        boolean slashStarComments, boolean slashSlashComments, boolean hashComments,
        boolean dashDashComments) {
      if (src == null) {
        return null;
      }

      StringBuffer buf = new StringBuffer(src.length());

      // It's just more natural to deal with this as a stream
      // when parsing..This code is currently only called when
      // parsing the kind of metadata that developers are strongly
      // recommended to cache anyways, so we're not worried
      // about the _1_ extra object allocation if it cleans
      // up the code

      StringReader sourceReader = new StringReader(src);

      int contextMarker = Character.MIN_VALUE;
      boolean escaped = false;
      int markerTypeFound = -1;

      int ind = 0;

      int currentChar = 0;

      try {
        while ((currentChar = sourceReader.read()) != -1) {

          if (false && currentChar == '\\') {
            escaped = !escaped;
          } else if (markerTypeFound != -1 && currentChar == stringCloses.charAt(markerTypeFound)
              && !escaped) {
            contextMarker = Character.MIN_VALUE;
            markerTypeFound = -1;
          } else if ((ind = stringOpens.indexOf(currentChar)) != -1 && !escaped
              && contextMarker == Character.MIN_VALUE) {
            markerTypeFound = ind;
            contextMarker = currentChar;
          }

          if (contextMarker == Character.MIN_VALUE && currentChar == '/'
              && (slashSlashComments || slashStarComments)) {
            currentChar = sourceReader.read();
            if (currentChar == '*' && slashStarComments) {
              int prevChar = 0;
              while ((currentChar = sourceReader.read()) != '/' || prevChar != '*') {
                if (currentChar == '\r') {

                  currentChar = sourceReader.read();
                  if (currentChar == '\n') {
                    currentChar = sourceReader.read();
                  }
                } else {
                  if (currentChar == '\n') {

                    currentChar = sourceReader.read();
                  }
                }
                if (currentChar < 0)
                  break;
                prevChar = currentChar;
              }
              continue;
            } else if (currentChar == '/' && slashSlashComments) {
              while ((currentChar = sourceReader.read()) != '\n' && currentChar != '\r'
                  && currentChar >= 0);
            }
          } else if (contextMarker == Character.MIN_VALUE && currentChar == '#' && hashComments) {
            // Slurp up everything until the newline
            while ((currentChar = sourceReader.read()) != '\n' && currentChar != '\r'
                && currentChar >= 0);
          } else if (contextMarker == Character.MIN_VALUE && currentChar == '-'
              && dashDashComments) {
            currentChar = sourceReader.read();

            if (currentChar == -1 || currentChar != '-') {
              buf.append('-');

              if (currentChar != -1) {
                buf.append(currentChar);
              }

              continue;
            }

            // Slurp up everything until the newline

            while ((currentChar = sourceReader.read()) != '\n' && currentChar != '\r'
                && currentChar >= 0);
          }

          if (currentChar != -1) {
            buf.append((char) currentChar);
          }
        }
      } catch (IOException ioEx) {
        // we'll never see this from a StringReader
      }

      return buf.toString();
    }


    // private static String removeBetweenWithSplitor(String sql, String start, String end) {
    // if (sql == null) {
    // return null;
    // }
    // int index0 = sql.indexOf(start);
    // if (index0 == -1) {
    // return sql;
    // }
    // int index1 = sql.indexOf(end, index0);
    // if (index1 == -1) {
    // return sql;
    // }
    // StringBuilder sb = new StringBuilder();
    // sb.append(sql.substring(0, index0));
    // sb.append(" ");
    // sb.append(sql.substring(index1 + end.length()));
    // return sb.toString();
    // }
    //
    // private static String getBetween(String sql, String start, String end) {
    // if (sql == null) {
    // return null;
    // }
    //
    // int index0 = sql.indexOf(start);
    // if (index0 == -1) {
    // return null;
    // }
    // int index1 = sql.indexOf(end, index0);
    // if (index1 == -1) {
    // return null;
    // }
    // return sql.substring(index0 + start.length(), index1).trim();
    // }
    //


  }


}
